#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

void mostrarVersion()
{
    printf("tp0 (66.20 Organizacion de Computadoras)\n");
    printf("Copyright © 2011\n");
    printf("Versión 1.0\n");
}

void mostrarAyuda ()
{
    printf("Usage:\n\n");
    printf("tp0 [options] from-file to-file\n");
    printf("tp0 - find differences between two files\n\n");
    printf("Options:\n");
    printf("  -v, --version      Print version and quit.\n");
    printf("  -h, --help         Print this information and quit.\n");
    printf("  -o, --output       Specifies the output file.\n");
    printf("  -m, --max          Shows the first m differences.\n");
}


/*
 * Si la opcion fue -m, el puntero a funcion pSeguir apunta a esta funcion que devuelve false
 * cuando la cantidadDeDiferencias computada por procesarArchivos supera el valor del parametro m
 */
int seguirConOpcionM(int m, int cantidadDeDiferencias)
{
	if (cantidadDeDiferencias == m)
		return 0;
	return 1;

}

/*
 *Se muestran las diferencias por archivo. Para ello se tienen en cuenta el nroLinea, el nroColumna y
 *si cambio o no de linea.
 */
void mostrarDiferenciaArchivo (int* cambioDeLinea, int nroLinea, int NroColumna){

}

/*
 *Se muestran las diferencias por pantalla. Para ello se tienen en cuenta el nroLinea, el nroColumna y
 *si cambio o no de linea.
 */
void mostrarDiferencia(int* cambioDeLinea, int nroLinea, int nroColumna)
{
	if(*cambioDeLinea)
	{
		if(nroLinea!=0)
			printf(".\n");
		printf("line: %d - Cols: %d", nroLinea,nroColumna);
		*cambioDeLinea = 0;
	}
	else
		printf(", %d",nroColumna);
}

/*
 * Cuando un archivo alcanza el fin de linea, pero el otro no, se recorre este ultimo contabilizando cada caracter
 * como una diferencia hasta el final de la linea.
 * */
void seguirConLineaRestante(FILE* fp, char* caracter, int* cambioDeLinea,int* nroLinea, int* nroColumna, int* cantidadDeDiferencias,int (*pSeguir)(int, int),int m)
{
	while((*caracter !='\n')&&(pSeguir(m,*cantidadDeDiferencias)))
	{
		mostrarDiferencia(cambioDeLinea, *nroLinea, *nroColumna);
		*cantidadDeDiferencias = *cantidadDeDiferencias + 1;
		*nroColumna = *nroColumna + 1;
		*caracter = fgetc(fp);
	}
	*nroLinea = *nroLinea + 1;
	*nroColumna = 0;
	*cambioDeLinea = 1;
}

/*
 * Cuando un archivo alcanza su EOF pero el otro no, se recorre este ultimo hasta el final contabilizando cada caracter
 * como una diferencia, linea por linea.
 * */
void seguirConArchivoRestante(FILE* fp, char* caracter, int* cambioDeLinea,int* nroLinea, int* nroColumna, int* cantidadDeDiferencias,int (*pSeguir)(int, int),int m)
{
	while(feof(fp) == 0)
	{
		seguirConLineaRestante(fp, caracter, cambioDeLinea, nroLinea, nroColumna, cantidadDeDiferencias, pSeguir,m);
		*caracter = fgetc(fp);
	}
}

/*
 * Recorre los archivos buscando las diferencias. pSeguir es un puntero a una funcion que le indica cuando dejar de
 * procesar. Si pSeguir==seguirConOpcionO se procesan los dos archivos hasta el final y si pSeguir==seguirConOpcionM
 * se recorren hasta encontrar la cantidad de diferencias indicadas en el parametro m
 */
void procesarArchivos(const char* nombreArchivoOriginal, const char* nombreArchivoModificado,int (*pSeguir)(int, int),int m)
{
	FILE *fpArchivoOriginal;
	fpArchivoOriginal = fopen(nombreArchivoOriginal,"r");
	FILE *fpArchivoModificado;
	fpArchivoModificado = fopen(nombreArchivoModificado,"r");

	/*Si no existe alguno de los archivos, se manda un error por stderr*/
	if ((fpArchivoOriginal == NULL)||(fpArchivoModificado == NULL))
	{
		if(fpArchivoOriginal == NULL)
			fprintf(stderr,"tp0: No se pudo abrir el archivo %s\n", nombreArchivoOriginal);
		if(fpArchivoModificado == NULL)
			fprintf(stderr,"tp0: No se pudo abrir el archivo %s\n", nombreArchivoModificado);
	}
	else
	{
		int nroLinea = 0;
		int nroColumna = 0;
		/*indica si se cambio o no de linea en el procesamiento*/
		int cambioDeLinea = 1;
		int cantidadDeDiferencias = 0;

		char caracterArchivoOriginal = fgetc(fpArchivoOriginal);
		char caracterArchivoModificado = fgetc(fpArchivoModificado);

		/*
		 * se recorren los dos archivos hasta que se termine alguno o se cumpla la condicion de la funcion a la que
		 * apunta el puntero a funcio pSeguir
		 */
		while ((caracterArchivoOriginal!=EOF)&&(caracterArchivoModificado!=EOF)&&(pSeguir(m,cantidadDeDiferencias)==1))
		{
			/*si cambio la linea del archivoOriginal se continua recorriendo el otro archivo hasta el fin de su linea*/
			if(caracterArchivoOriginal == '\n')
				/*se procesa los caracteres restantes de la linea del archivoModificado*/
				seguirConLineaRestante(fpArchivoModificado, &caracterArchivoModificado, &cambioDeLinea, &nroLinea, &nroColumna, &cantidadDeDiferencias, pSeguir, m);
			else
			{
				/*si cambio la linea del archivoModificado se continua recorriendo el otro archivo hasta el fin de su linea*/
				if(caracterArchivoModificado == '\n')
					/*se procesa los caracteres restantes de la linea del archivoOriginal*/
					seguirConLineaRestante(fpArchivoOriginal, &caracterArchivoOriginal, &cambioDeLinea, &nroLinea, &nroColumna, &cantidadDeDiferencias, pSeguir, m);
				else
				{
					if(caracterArchivoOriginal!=caracterArchivoModificado)
					{
						mostrarDiferencia(&cambioDeLinea, nroLinea, nroColumna);
						cantidadDeDiferencias++;
					}
					nroColumna++;
				}
			}
			/*se leen los siguientes caracteres*/
			caracterArchivoOriginal = fgetc(fpArchivoOriginal);
			caracterArchivoModificado = fgetc(fpArchivoModificado);
		}

		/*se termino archivoModificado pero no archivoOriginal*/
		if ((feof(fpArchivoOriginal) == 0)&&(feof(fpArchivoModificado) != 0))
			seguirConArchivoRestante(fpArchivoOriginal, &caracterArchivoModificado, &cambioDeLinea,&nroLinea, &nroColumna, &cantidadDeDiferencias, pSeguir, m);
		else
			/*se termino archivoOriginal pero no archivoModificado*/
			if((feof(fpArchivoOriginal) != 0)&&(feof(fpArchivoModificado) == 0))
				seguirConArchivoRestante(fpArchivoModificado, &caracterArchivoModificado, &cambioDeLinea,&nroLinea, &nroColumna, &cantidadDeDiferencias, pSeguir, m);

		printf(".\n");
		fclose(fpArchivoOriginal);
		fclose(fpArchivoModificado);
	}

}

int main (int argc, char* argv[]){

	const char* const opcionesCortas = "vho:m:";

	const struct option opcionesLargas[] =
	{
		{"version",    0, NULL, 'v'},
		{"help",       0, NULL, 'h'},
		{"output",     1, NULL, 'o'},
		{"max",        1, NULL, 'm'},
		{NULL,         0, NULL, 0}
	};

	char* archivoEntrada1;
	char* archivoEntrada2;
	char* archivoSalida = NULL;
	int limiteErrores = -1;
	int siguienteOpcion = 0;


	/*El siguiente bucle inicializa las variables de acuerdo a las opciones enviadas por consola*/
	while (siguienteOpcion != -1){

		siguienteOpcion = getopt_long(argc, argv, opcionesCortas, opcionesLargas, NULL);

		switch (siguienteOpcion){

		case 'h':
			mostrarAyuda();
			exit(0);

		case 'v':
			mostrarVersion();
			exit(0);

		case 'o':
			archivoSalida = optarg;
			break;

		case 'm':
			limiteErrores = atoi(optarg);
			if (limiteErrores <= 0){
				fprintf(stderr, "Parámetro inválido: -m");
				exit (1);
			}
			break;

		}

	}

	/*Toma de parámetros del programa*/
	int cantidadParametros = argc - optind; //Contiene la cantidad de parámetros que se enviaron

	if (cantidadParametros !=2){
		fprintf(stderr, "Cantidad de parámetros incorrecta");
		exit(1);
	}else{
		archivoEntrada1 = argv[optind];
		archivoEntrada2 = argv[optind+1];
	}

	/*Puntero a la función de validación*/
	int (*pSeguir)(int, int) = seguirConOpcionM;


	procesarArchivos(archivoEntrada1, archivoEntrada2, pSeguir, limiteErrores);

	return 0;

}

